﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pata.SylkSharp.Core.Utils;

namespace Pata.SylkSharp.Core.Bll
{
    /// <summary>
    /// Represent the base class for record of an SLK file. A line.
    /// </summary>
    /// <typeparam name="TOfFields">The type of fields contained by this record.</typeparam>
    /// <typeparam name="TEnumForFields">The enumeration that defines values of this record.</typeparam>
    public abstract class RecordBase<TOfFields, TEnumForFields> : IRecord
                                                        where TOfFields : FieldBase<TEnumForFields>, new()
                                                        where TEnumForFields : struct
    {
        #region Fields
        private readonly RecordType _type;
        #endregion Fields

        #region Constructors
        /// <summary>
        /// Instanciate a new <see cref="RecordBase&lt;TOfFields, TEnumForFields&gt;"/>
        /// </summary>
        /// <param name="pType">Type of the record.</param>
        protected RecordBase(RecordType pType)
        {
            this._type = pType;
        }

        /// <summary>
        /// Instanciate a new <see cref="RecordBase&lt;TOfFields, TEnumForFields&gt;"/>
        /// </summary>
        /// <param name="pType">Type of the record.</param>
        /// <param name="pLineWithoutId">A line of a SYLK file without the RecordType</param>
        /// <param name="pUnknowType">The default field type to assign if no corresponding type is found.</param>
        protected RecordBase(RecordType pType, string pLineWithoutId, TEnumForFields pUnknowType)
        {
            this._type = pType;
            this.Fields = this.ParseFields(pLineWithoutId, pUnknowType).ToList();
            List<TEnumForFields> missingFields = this.GetMissingFields().ToList();
            //TODO:Implement parsing options, to avoid systematic exception when a field is missing.
            //if (missingFields.Count > 0)
            //    throw new MissingSylkFieldException<TEnumForFields>(pType, missingFields);
        }
        
        #endregion Constructors

        #region Properties
        /// <summary>
        /// Get the list of Fields.
        /// </summary>
        public List<TOfFields> Fields { get; protected set; }

        /// <summary>
        /// Get the type of this record defined by the <see cref="RecordType"/> enumeration.
        /// </summary>
        public RecordType Type 
        { 
            get { return _type; } 
        }

        #endregion Properties

        #region Methods
        /// <summary>
        /// Returns Fields in this line.
        /// </summary>
        /// <param name="pLineWithoutId">A line of a SYLK file without the RecordType</param>
        /// <param name="pDefaultValue">The default value for the field if it cannot be parsed.</param>
        protected IEnumerable<TOfFields> ParseFields(string pLineWithoutId, TEnumForFields pDefaultValue)
        {
            string[] fields = pLineWithoutId.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string field in fields)
            {
                yield return new TOfFields()
                {
                    Type = EnumParser.Parse<TEnumForFields>(field.Substring(0, 1), pDefaultValue)
                    ,
                    Value = field
                };
            }
        }

        /// <summary>
        /// Indicates if this field is present.
        /// </summary>
        /// <param name="pField"></param>
        /// <returns></returns>
        public bool HaveField(TEnumForFields pField)
        {
            return this.Fields.Exists(field => field.Type.Equals(pField));
        }

        /// <summary>
        /// Get the mandatory field list.
        /// </summary>
        /// <returns></returns>
        public abstract IEnumerable<TEnumForFields> GetMandatoryFields();

        /// <summary>
        /// Get the missing mandatory fields.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<TEnumForFields> GetMissingFields()
        {
            return this.GetMandatoryFields().Except(this.Fields.Select(field => field.Type));
        }

        /// <summary>
        /// Get the specified field, return null if not found, throws an exception if many are found.
        /// </summary>
        /// <param name="pValue">The type of field to be found.</param>
        /// <returns></returns>
        public TOfFields GetField(TEnumForFields pValue)
        {
            return this.GetField(pValue, false);
        }

        /// <summary>
        /// Get the specified field, return null if not found, throws an exception if many are found.
        /// </summary>
        /// <param name="pValue">The type of field to be found.</param>
        /// <param name="pThrowExceptionIfNotFound">Indicates if an exception shall be thrown if the field is not found.</param>
        /// <returns></returns>
        public TOfFields GetField(TEnumForFields pValue, bool pThrowExceptionIfNotFound)
        {
            return pThrowExceptionIfNotFound
                ? this.Fields.Single(f => f.Value.Equals(pValue))
                : this.Fields.SingleOrDefault(f => f.Value.Equals(pValue));
        }

        /// <summary>
        /// Get the default field type.
        /// </summary>
        /// <returns></returns>
        public abstract TEnumForFields GetUnknownFieldType();
        #endregion Methods

        #region IRecord explicit implementation
        
        IEnumerable<IField> IRecord.Fields
        {
            get
            {
                return this.Fields.Cast<IField>();
            }
        }
        
        object IRecord.GetUnknownFieldType()
        {
            return this.GetUnknownFieldType();
        }
        #endregion  IRecord explicit implementation
    }
}
